#include "stdafx.h"
#include "CScrLanguage.h"
#include "CScripts.h"
#include "CTokens.h"
#include "CParser.h"
#include "CCompiler.h"

CCompiler	Compiler;

CLinkPtr<const CToken*>	CCompiler::s_pTokens,
						CCompiler::s_pBadToken;
CFile				*	CCompiler::s_pOutFile;
CSubScript			*	CCompiler::s_pSubScript;
CScript				*	CCompiler::s_pScript;
int						CCompiler::s_nNumBytesWritten,
						CCompiler::s_nNumLinesEncoded,
						CCompiler::s_nNumTokensEncoded,
						CCompiler::s_nNumNamelessScripts,
						CCompiler::s_nVarDeclarationType,		// (basic type)
						CCompiler::s_nNumBytesBuffered;
DWORD					CParser::s_dwLineOffsets[],
						CCompiler::s_dwLabelOffset;
BYTE				*	CCompiler::s_pCompilationBuffer;
CParser::sAsmStruct	*	CCompiler::s_pAsmStruct;

int CCompiler::Compile(CSubScript *pSubScript, CFile * pOutFile)
{
	CScriptFile * pScriptFile = pSubScript->GetFile();
	s_pScript = pSubScript->GetScript();
	s_pSubScript = pSubScript;
	s_pOutFile = pOutFile;

	s_dwLabelOffset = pSubScript->GetParseInfo()->GetBeginOffset();
	
	try
	{
		WORD wOpcodeMask = 0;

		// Next...
		while(auto pToken = GetNextCompilationToken())
		{
			if(pToken->GetType() == TOKEN_CONSTRUCT)
			{
				// Set some vars based on keywords* - need to add some configurability to this?
				switch(pToken->GetConstruct())
				{
				case SCR_NOT:		// operator...
					wOpcodeMask = OPCODE_NOT_MASK;
					continue;
				}
			}
			else
			{
				DWORD dwOldNumBytes = 0;

				// With most things, we don't have to use type translations.. they're just for parameters
				switch(pToken->GetType())
				{
				case TOKEN_ASM:
					// Write out what we read from the assembler output
					s_pAsmStruct = &Parser.s_AsmStructs[pToken->GetIntValue()];
					s_nNumBytesWritten += pOutFile->Write(s_pAsmStruct->m_pData, s_pAsmStruct->m_dwActualSize);
					s_nNumLinesEncoded = s_pAsmStruct->m_nEndLine;
					break;
				case TOKEN_COMMAND:
				case TOKEN_OPERATOR:
					// Write the opcode ID then enter the argument list
					s_nNumBytesWritten += pOutFile->Write<tOpcode>((tOpcode)pToken->GetOpcodeValue()->GetId() | wOpcodeMask);
					WriteParams(pToken);
					wOpcodeMask = 0;
					break;
				case TOKEN_HEX_BYTE:
					// Single hex byte, easy
					s_nNumBytesWritten += pOutFile->Write<BYTE>(pToken->GetIntValue());
					break;
				case TOKEN_HEX_WORD:
					// Single hex word, easy
					s_nNumBytesWritten += pOutFile->Write<WORD>(pToken->GetIntValue());
					break;
				case TOKEN_HEX_DWORD:
					// Single hex dword, easy
					s_nNumBytesWritten += pOutFile->Write<DWORD>(pToken->GetIntValue());
					break;
				case TOKEN_VALUE:
					// Not necessarily a parameter, but it's here and we might as well write it...
					switch(pToken->GetValueType())
					{
					case PT_INT:
						// Unlike hex values, we're dynamic now...
						switch(GetIntNumBytes(pToken->GetIntValue()))
						{
						case 1:
							s_nNumBytesWritten += pOutFile->Write<char>(pToken->GetIntValue());
							break;
						case 2:
							s_nNumBytesWritten += pOutFile->Write<short>(pToken->GetIntValue());
							break;
						default:
							s_nNumBytesWritten += pOutFile->Write(pToken->GetIntValue());
							break;
						}
						break;
					case PT_FLOAT:
						s_nNumBytesWritten += s_pOutFile->Write(pToken->GetFloatValue());
						break;
					}
					break;
				case TOKEN_STRING:
					// Write a plain string, no funny business
					s_nNumBytesWritten += pOutFile->Write(pToken->GetString(), pToken->GetStringLength());
					break;
				case TOKEN_LABEL:
					WriteValue(-((int)(pToken->GetLabelValue()->GetOffset() + s_dwLabelOffset)), DT_INT32);
					break;
				case TOKEN_VAR:
				case TOKEN_TYPENAME:
					// Nothing to do with us...
					break;
				case TOKEN_HEXMAYBELABEL:
				case TOKEN_UNKNOWN:
					// Preparser error
					throw("WTF");
				}
			}
		}

		s_pSubScript->SetOutFilePath(s_pOutFile->GetName());
		delete s_pOutFile;
		return s_nNumBytesWritten;
	}
	catch(const char *e)
	{
		printf("\n");
		Error(e);
		delete s_pOutFile;
	}
	return -1;
}

void CCompiler::WriteParams(const CToken *pCommandToken)
{
	// Are we compiling just two params (operator) or a list of them (command)
	switch(pCommandToken->GetType())
	{
	case TOKEN_OPERATOR:
		{
			auto pOperation = pCommandToken->GetOperationValue();
			WriteParam(pOperation->GetLeftArg()->GetType(), GetNextCompilationToken());
			WriteParam(pOperation->GetRightArg()->GetType(), GetNextCompilationToken());
			break;
		}
	case TOKEN_COMMAND:
		{
			auto pCommand = pCommandToken->GetCommandValue();
			auto args = pCommand->GetArgs();
			for(auto pLink = args->Begin(); pLink != args->Bad(); ++pLink)
			{
				WriteParam(pLink->GetType(), GetNextCompilationToken());
			}
			break;
		}
	}
}
void CCompiler::WriteParam(const CTypeBase * pType, const CToken *pToken)
{
	static char szBuffer[512];
	if(pType) pType = pType->Standardise();

	// We have a translation for this?
	if(auto pTranslation = pToken->GetTranslation())
	{
		// Get any available sub-token (usually array subscripts)
		auto pSubToken = pToken->GetSubToken();
		// Get any available variable
		auto pVariable = pToken->GetType() == TOKEN_VAR ? pToken->GetVariableValue() : nullptr;

		// Write each element of the translation (dont miss a single WORD)
		for(int i = 0, j = pTranslation->GetNumElements(); i < j; ++i)
		{
			// Get element type and value
			auto elem = pTranslation->GetElement(i);
			auto val = pTranslation->GetElementValue(i);

			// Depending on the type, the value could mean a number of things (yeah, we're pretty much SCM parsing)
			switch(elem)
			{
			case TRANSLATE_ELEM_OPERAND:
				// The value refers to an operand (a value from either the token or sub-token)
				switch(val)
				{
				case TRANSLATE_OPERAND_VAL:
					// Operand is a value - write the value from the token depending on the basic type
					switch(pToken->GetValueType())
					{
					case PT_NONE:
					case PT_INT:
					case PT_CONST:
					case PT_FLOAT:
						// A value? Write it depending on the data type from the translation...
						// The types.def config specifies the translation and the data types in the conditions,
						// so GetDataType() is just GetConditionValue with appropriate casting
						switch(pToken->GetType())
						{
						case TOKEN_LABEL:
							// If it's a label, get the label offset
							WriteValue(-((int)(pToken->GetLabelValue()->GetOffset() + s_dwLabelOffset)), pTranslation->GetDataType(0));
							break;
						case TOKEN_COMMAND:
							// If it's a command, get the opcode ID
							WriteValue((tOpcode)pToken->GetOpcodeValue()->GetId(), pTranslation->GetDataType(0));
							break;
						default:
							// Basic int or float value
							if(pToken->GetValueType() & PT_FLOAT) WriteValue(pToken->GetFloatValue(), pTranslation->GetDataType(0));
							else WriteValue(pToken->GetIntValue(), pTranslation->GetDataType(0));
							break;
						}
						break;
					case PT_TEXT_LABEL:
					case PT_STRING:
						// Write a string - we wouldn't be here if there wasn't enough space
						s_pOutFile->Write(pToken->GetString(), pToken->GetValueSize());

						// If the size specified in the config is const, then that amount of bytes MUST be written
						if(pTranslation->IsConditionSizeConst(0))
						{
							// Have we got spare bytes left?
							int nBytesLeft = pTranslation->GetConditionValue(0) - pToken->GetValueSize();
							if(nBytesLeft > 0)
							{
								// Fill the void!
								if(nBytesLeft > sizeof(szBuffer)) nBytesLeft = sizeof(szBuffer);
								memset(szBuffer, 0, nBytesLeft);                                     
								s_pOutFile->Write(szBuffer, nBytesLeft);
							}
						}
						break;
					}
					break;
				case TRANSLATE_OPERAND_IDXVAR:
					// An array subscript needs writing, which is stored as a sub-token for convinience...
					switch(pSubToken->GetType())
					{
					case TOKEN_VAR:
						// The subscript is a real, declared var - this is the 2nd operand, hence the data type index
						WriteValue(pSubToken->GetVariableValue()->GetIndex(), pTranslation->GetDataType(1));
						break;
					case TOKEN_VARINDEX:
						// The subscript is actually an index we've calculated ourselves (perhaps a non-array var with a constant subscript)
						WriteValue(pSubToken->GetIntValue(), pTranslation->GetDataType(1));
						break;
					}
					break;
				case TRANSLATE_OPERAND_VAR:
					// A variable index simply needs to be written
					switch(pToken->GetType())
					{
					case TOKEN_VAR:
						// Real var, write it's index...
						WriteValue(pToken->GetVariableValue()->GetIndex(), pTranslation->GetDataType(0));
						break;
					case TOKEN_VARINDEX:
						// Not a real var, but the int represents a var index...
						WriteValue(pToken->GetIntValue(), pTranslation->GetDataType(0));
						break;
					}
					break;
				}
				break;
			case TRANSLATE_ELEM_VAL:
				// The value refers to a.. well, it's just a value, actually...
				switch(GetDWordNumBytes(val))
				{
				case 1: s_nNumBytesWritten += s_pOutFile->Write<BYTE>(val); break;
				case 2: s_nNumBytesWritten += s_pOutFile->Write<WORD>(val); break;
				case 4: s_nNumBytesWritten += s_pOutFile->Write<DWORD>(val); break;
				}
				break;
			case TRANSLATE_ELEM_COND:
				// The value refers to a condition, which refers to a value, as everything does
				switch(val)
				{
				case TRANSLATE_COND_NONE:
				default:
					// Whhaaaat?
					break;
				case TRANSLATE_COND_SIZEOF_INDEXVAR:
					// Condition is the size of an array subscript var index.. we do have a subscript, right?
					if(pSubToken)
					{
						// Case 2 and 4 are useless until we break the technical size restriction of 255 bytes
						switch(GetIntNumBytes(pSubToken->GetValueSize()))
						{
						case 1: s_nNumBytesWritten += s_pOutFile->Write<BYTE>((BYTE)pSubToken->GetValueSize()); break;
						case 2: s_nNumBytesWritten += s_pOutFile->Write<WORD>(pSubToken->GetValueSize()); break;
						case 4: s_nNumBytesWritten += s_pOutFile->Write<DWORD>(pSubToken->GetValueSize()); break;
						}
					}
					break;
				case TRANSLATE_COND_SIZEOF_ARRAY:
					// Condition is the size of the array.. as in, number of elements
					if(pVariable)
					{
						switch(GetDWordNumBytes(pVariable->GetArraySize()))
						{
						case 1: s_nNumBytesWritten += s_pOutFile->Write<BYTE>(pVariable->GetArraySize()); break;
						case 2: s_nNumBytesWritten += s_pOutFile->Write<WORD>(pVariable->GetArraySize()); break;
						case 4: s_nNumBytesWritten += s_pOutFile->Write<DWORD>(pVariable->GetArraySize()); break;
						}
					}
					break;
				case TRANSLATE_COND_SIZEOF_VAL:
				case TRANSLATE_COND_STRLEN_STR:
				case TRANSLATE_COND_SIZEOF_VAR:
					// Condition is the size of a value or var index, which are all part of this token - we got are 3-in-one situation!
					switch(GetIntNumBytes(pToken->GetValueSize()))
					{
					case 1: s_nNumBytesWritten += s_pOutFile->Write<BYTE>((BYTE)pToken->GetValueSize()); break;
					case 2: s_nNumBytesWritten += s_pOutFile->Write<WORD>(pToken->GetValueSize()); break;
					case 4: s_nNumBytesWritten += s_pOutFile->Write<DWORD>(pToken->GetValueSize()); break;
					}
					break;
				}
			} // case TRANSLATE_ELEM_COND:
		} // loop elements
		return;
	}

	// fail
	THROW("No valid translation found for type '%s'", pType->GetName());
}

void CCompiler::WriteValue(int val, eDataType size)
{
	// Writes int values
	switch(size)
	{
	case DT_NULL:
		s_nNumBytesWritten += s_pOutFile->Write<BYTE>(0);
	default:
		break;
	case DT_FLOAT8:
	case DT_FLOAT16:
	case DT_FLOAT24:
	case DT_FLOAT32:
	case DT_FIXED16:
		WriteValue((float)val, size);
		break;
	case DT_INT8:
		s_nNumBytesWritten += s_pOutFile->Write<BYTE>(val);
		break;
	case DT_INT16:
		s_nNumBytesWritten += s_pOutFile->Write<WORD>(val);
		break;
	case DT_INT24:
		s_nNumBytesWritten += s_pOutFile->Write<WORD>(val);
		s_nNumBytesWritten += s_pOutFile->Write<BYTE>(val >> 16);
		break;
	case DT_INT32:
		s_nNumBytesWritten += s_pOutFile->Write<DWORD>(val);
		break;
	}
}
void CCompiler::WriteValue(float val, eDataType size)
{
	// Writes float values
	switch(size)
	{
	case DT_NULL:
		s_nNumBytesWritten += s_pOutFile->Write<BYTE>(0);
	default:
		break;
	case DT_INT8:
	case DT_INT16:
	case DT_INT24:
	case DT_INT32:
		WriteValue((int)val, size);
		break;
	case DT_FIXED16:
		s_nNumBytesWritten += s_pOutFile->Write<WORD>((short)((int)val * 16.0));
		break;
	case DT_FLOAT8:
		s_nNumBytesWritten += s_pOutFile->Write<BYTE>((char)(*(int*)&val >> 24));
		break;
	case DT_FLOAT16:
		s_nNumBytesWritten += s_pOutFile->Write<WORD>((short)(*(int*)&val >> 16));
		break;
	case DT_FLOAT24:
		s_nNumBytesWritten += s_pOutFile->Write<WORD>((short)(*(int*)&val));
		s_nNumBytesWritten += s_pOutFile->Write<BYTE>((char)(*(int*)&val >> 16));
		break;
	case DT_FLOAT32:
		s_nNumBytesWritten += s_pOutFile->Write<float>(val);
		break;
	}
}